#include <zmq.hpp>
#include <string>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "zmqMsg.pb.h"
#include <thread>


using namespace cv;
using namespace std;

bool ifStop = false;

int64 work_begin;
double work_fps;

void workBegin()
{
    work_begin = cv::getTickCount();
}

void workEnd()
{
    int64 delta = cv::getTickCount() - work_begin;
    double freq = cv::getTickFrequency();
    work_fps = freq / delta;
}


void serializeMatToString(const cv::Mat& image, std::string &str) {
    zmqMsg::Image imageMsg;
    uint cols = image.cols;
    uint rows = image.rows;
    uint size= cols*rows;
    uint channelNum = image.channels();
    std::vector<cv::Mat> mats(channelNum);


    imageMsg.set_width(cols);
    imageMsg.set_height(rows);
    imageMsg.set_channelnum(channelNum);

    if(channelNum >1)
    {
        cv::split(image,mats);
    }
    else {
        mats[0] = image;
    }

    for(uint j=0;j<channelNum;j++)
    {
        uchar buffer[size];
        for(uint i=0; i< rows; i++) {
            const uchar* rowDataPtr =mats[j].ptr<uchar>(i);
            memcpy(buffer+i*cols,rowDataPtr,cols);

        }
        switch(j){
        case 0:
            imageMsg.set_buffer0(buffer,size);
            break;
        case 1:
            imageMsg.set_buffer1(buffer,size);
            break;
        case 2:
            imageMsg.set_buffer2(buffer,size);
            break;
        default:
            break;
        }

    }

    imageMsg.SerializeToString(&str);

}

void drawImage(cv::Mat& src, const zmqMsg::CircleMark& msg)
{

    cv::Mat srcBGR;
    if(src.channels()>1){
        srcBGR = src;
    } else {
        cv::cvtColor(src,srcBGR,CV_GRAY2BGR,3);
    }

    uint size = msg.roicircle_size();

    for(uint i=0; i<size;i++)
    {
        const zmqMsg::CircleMark::Circle& circleMsg= msg.roicircle(i);
        uint centerX = circleMsg.centerx();
        uint centerY = circleMsg.centery();
        uint radius = circleMsg.radius();
        bool ifWrong = circleMsg.ifwrong();

        if(ifWrong)
            cv::circle(srcBGR,cv::Point(centerX,centerY),radius,Scalar(0, 0, 255),4);
        else
            cv::circle(srcBGR,cv::Point(centerX,centerY),radius,Scalar(0, 255, 0),4);

        string note= circleMsg.note();

        if(note !="") {
            if(ifWrong)
                cv::putText(srcBGR, note, cv::Point(centerX-radius/2-40, centerY-radius-20), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 3);
            else
                cv::putText(srcBGR, note, cv::Point(centerX-radius/2-40, centerY-radius-20), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 3);
        }

    }


    std::ostringstream fpsText;
    fpsText << "FPS: " << work_fps;
    cv::putText(srcBGR, fpsText.str(), cv::Point(5, 100), cv::FONT_HERSHEY_SIMPLEX, 3.2, cv::Scalar(255, 255, 255), 3);

    cv::Mat showMat;
    cv::resize(srcBGR, showMat,cv::Size(1280,960));
    cv::imshow("result",showMat);

}

void handleKey(uchar key)
{
    switch (key)
    {
    case 'c':
    case 'C':
        ifStop = !ifStop;
        std::cout << "start or stop";
        break;

    default:
        break;
    }

}

zmq::socket_t createReqSocket(zmq::context_t& context) {
    zmq::socket_t socket(context, ZMQ_REQ);
    socket.setsockopt(ZMQ_RCVTIMEO,3000);
    socket.connect ("tcp://127.0.0.1:5555");
    return socket;
}

zmq::socket_t createGetReqListSocket(zmq::context_t& context) {
    zmq::socket_t socket(context, ZMQ_REQ);
    socket.setsockopt(ZMQ_RCVTIMEO,3000);
    socket.connect("tcp://127.0.0.1:5559");
    return socket;
}

void processGetReqList(zmq::context_t& context) {
    zmq::message_t reqListMsg;
    while(true)
    {
        zmq::socket_t getReqListSocket = createGetReqListSocket(context);
        zmq::message_t request;
        getReqListSocket.send(request);


        if(!getReqListSocket.recv(&reqListMsg)) {
            std::cout<<"server doesn't reply. Keep getting reqList"<<std::endl;

            continue;
        }
        break;
    }
    zmqMsg::RequestList reqList;
    reqList.ParseFromArray(reqListMsg.data(),reqListMsg.size());

    uint size = reqList.list_size();

    for(uint i=0; i< size; i++) {
        zmqMsg::RequestList::Request item = reqList.list(i);
        cout<<"Req Id:"<<item.requesttype()<<std::endl;
        cout<<"Req Description:"<< item.requestdescription()<<std::endl;
    }
}

void mainLoop(zmq::context_t& context) {
    zmq::socket_t reqSocket = createReqSocket(context);
    cv::Mat origin;

    std::string imageName="/home/jimmy/code/data/Image60.bmp";

    cv::namedWindow("result",cv::WINDOW_AUTOSIZE);
    while(true) {
        origin = cv::imread(imageName, IMREAD_UNCHANGED);

        if(!ifStop) {
            workBegin();

            std::string str;
            serializeMatToString(origin, str);
            uint size=str.length();
            zmq::message_t request(size);
            memcpy(request.data(),str.c_str(),size);
            reqSocket.send(request);

            zmq::message_t reply;
            if(!reqSocket.recv(&reply)) {
                std::cout<<"server doesn't reply"<<std::endl;
                reqSocket = createReqSocket(context);
                continue;
            }

            zmqMsg::CircleMark resultMsg;
            resultMsg.ParseFromArray(reply.data(),reply.size());

            workEnd();


            drawImage(origin,resultMsg);
        }

        uchar key = cv::waitKey(1);
        handleKey(key);

    }
}

void subscribeUpdateReqList(zmq::context_t& context)
{
    zmq::socket_t socket(context, ZMQ_SUB);
    socket.connect("tcp://127.0.0.1:5565");
    cout<<"I am listening req list update"<<std::endl;
    socket.setsockopt(ZMQ_SUBSCRIBE, "");
    while (true) {
        zmq::message_t update;
        socket.recv(&update);
        cout<<"get update msg"<<endl;
        processGetReqList(context);

    }
}

int main ()
{
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    //  Prepare our context
    zmq::context_t context(3);
    std::thread subThread(&subscribeUpdateReqList,std::ref(context));


    std::cout << "Connecting to processing server…" << std::endl;
    processGetReqList(context);

    mainLoop(context);

    return 0;
}
